use core::ptr::copy_nonoverlapping;

use crate::frame::{Cmd, Proto, Res, Res2, ToEc};

pub trait ProtoEncode2 {
    unsafe fn encode(self, ptr: *mut u8) -> usize;
}

impl <T: ProtoEncode2, E: ToEc> ProtoEncode2 for Res2<T, E> {
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        *ptr = self.cmd;
        match self.ret {
            Ok(v) => {
                *ptr.add(1) = 0;
                2 + v.encode(ptr.add(2))
            },
            Err(e) => {
                *ptr.add(1) = e.to_ec();
                2
            }
        }
    }
}

impl <T: ProtoEncode2> ProtoEncode2 for Res<T> {
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        *ptr = self.cmd;
        *ptr.add(1) = self.ec;
        self.body.encode(ptr.add(2)) + 2
    }
}

impl <T: ProtoEncode2> ProtoEncode2 for Cmd<T> {
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        *ptr = self.cmd;
        self.body.encode(ptr.add(1)) + 1
    }
}

impl <T: ProtoEncode2> ProtoEncode2 for Proto<T> {
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        *ptr = self.ty;
        *ptr.add(1) = self.seq;
        self.body.encode(ptr.add(2)) + 2
    }
}

impl<T: ProtoEncode2 + Copy> ProtoEncode2 for &T {
    #[inline(always)]
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        (*self).encode(ptr)
    }
}

impl ProtoEncode2 for u8 {
    #[inline(always)]
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        *ptr = self;
        1
    }
}

impl ProtoEncode2 for u16 {
    #[inline(always)]
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        *ptr = (self >> 8) as u8;
        *ptr.add(1) = self as u8;
        2
    }
}

impl ProtoEncode2 for u32 {
    #[inline(always)]
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        *ptr = (self >> 24) as u8;
        *ptr.add(1) = (self >> 16) as u8;
        *ptr.add(2) = (self >> 8) as u8;
        *ptr.add(3) = self as u8;
        4
    }
}

impl ProtoEncode2 for &[u8] {
    #[inline(always)]
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        let len = self.len();
        *ptr = len as u8;
        copy_nonoverlapping(self.as_ptr(), ptr.add(1), len);
        len + 1
    }
}

impl ProtoEncode2 for i8 {
    #[inline(always)]
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        *ptr = self as u8;
        1
    }
}

impl ProtoEncode2 for i16 {
    #[inline(always)]
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        *ptr = (self >> 8) as u8;
        *ptr.add(1) = self as u8;
        2
    }
}

impl ProtoEncode2 for i32 {
    #[inline(always)]
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        *ptr = (self >> 24) as u8;
        *ptr.add(1) = (self >> 16) as u8;
        *ptr.add(2) = (self >> 8) as u8;
        *ptr.add(3) = self as u8;
        4
    }
}

impl ProtoEncode2 for () {
    #[inline(always)]
    unsafe fn encode(self, _ptr: *mut u8) -> usize {
        0
    }
}

impl<T1: ProtoEncode2, T2: ProtoEncode2> ProtoEncode2 for (T1, T2) {
    #[inline(always)]
    unsafe fn encode(self, ptr: *mut u8) -> usize {
        let n1 = self.0.encode(ptr);
        let n2 = self.1.encode(ptr.add(n1));
        n1 + n2
    }
}
